//
// Created by laichao on 2023/9/10.
//

#include <list>
#include <memory>
#include <iostream>
#include <string>
#include <utility>
#include <algorithm>

class Observer;

// Abstract Subject class
class Subject {
public:
    Subject() = default;

    virtual ~Subject() = default;

    virtual void attach(const std::shared_ptr<Observer> &observer) = 0;

    virtual void detach(const std::shared_ptr<Observer> &observer) = 0;

    virtual void notify() = 0;

    virtual int get_status() const { return _status; }

    void set_status(const int &status) {
        _status = status;
        std::printf("Status update: %d\n", status);
    }

protected:
    int _status = 0;
    std::list<std::shared_ptr<Observer>> _observer_objects;
};

// Abstract Observer class
class Observer {
public:
    Observer() = delete;

    explicit Observer(std::shared_ptr<Subject> subject, std::string name = "")
            : _subject(std::move(subject)), _name(std::move(name)) {}

    virtual ~Observer() = default;

    virtual void update() = 0;

    virtual const std::string &name() { return _name; }

protected:
    std::shared_ptr<Subject> _subject;
    std::string _name;
};

// concrete subject class
class ConcreteSubject : public Subject {
public:
    void attach(const std::shared_ptr<Observer> &observer) override {
        const auto &iter = std::find(_observer_objects.begin(), _observer_objects.end(), observer);
        if (iter == _observer_objects.end()) {
            _observer_objects.emplace_back(observer);
            std::printf("Attach observer: %s\n", observer->name().c_str());
        }
    }

    void detach(const std::shared_ptr<Observer> &observer) override {
        _observer_objects.remove(observer);
        printf("Detach observer: %s\n", observer->name().c_str());
    }

    void notify() override {
        auto iter = _observer_objects.begin();
        while (iter != _observer_objects.end()) {
            (*iter)->update();
            ++iter;
        }
    }
};

// concrete observer class
class ObserverJack : public Observer {
public:
    explicit ObserverJack(const std::shared_ptr<Subject> &subject, const std::string &name = "unknown")
            : Observer(subject, name) {}

    void update() override {
        std::printf("Observer: %s, get update info msg, status is: %d\n",
                    _name.c_str(), _subject->get_status());
    }
};

// concrete observer class
class ObserverRose : public Observer {
public:
    explicit ObserverRose(const std::shared_ptr<Subject> &subject, const std::string &name = "unknown")
            : Observer(subject, name) {}

    void update() override {
        std::printf("Observer: %s, get update info msg, status is: %d\n",
                    _name.c_str(), _subject->get_status());
    }
};

int main() {
    std::shared_ptr<Subject> subject = std::make_shared<ConcreteSubject>(); // create a subject

    // create some observers
    auto observer_jack_one = std::make_shared<ObserverJack>(subject, "jack_fans_one");
    auto observer_jack_two = std::make_shared<ObserverJack>(subject, "jack_fans_two");
    auto observer_jack_three = std::make_shared<ObserverJack>(subject, "jack_fans_three");

    auto observer_rose_one = std::make_shared<ObserverJack>(subject, "rose_fans_one");
    auto observer_rose_two = std::make_shared<ObserverJack>(subject, "rose_fans_two");
    auto observer_rose_three = std::make_shared<ObserverJack>(subject, "rose_fans_three");

    // register observers
    subject->attach(observer_jack_one);
    subject->attach(observer_jack_two);
    subject->attach(observer_jack_three);
    subject->attach(observer_rose_one);
    subject->attach(observer_rose_two);
    subject->attach(observer_rose_three);

    subject->set_status(2); // subject change status
    subject->notify();

    std::cout << std::string(50, '-') << std::endl;

    // detach one observer of jack and rose
    subject->detach(observer_jack_one);
    subject->detach(observer_rose_one);

    subject->set_status(3); // subject change status
    subject->notify();

    return 0;
}
